Wskaźniki w VB

Zacznijmy od tego, czym są wskaźniki. Wskaźnik to taki adres funkcji. Otóż każda procedura wykonywalna Windows (taka mogąca utworzyć wątek) ma swój unikalny adres, tak samo jest w przypadku zmiennych (tych umieszczonych w pamięci operacyjnej). Wbudowane funkcje do obsługi wskaźników dla zmiennych to VarPtr, StrPtr lub ObjPtr (zależnie od typu zmiennej). Co daje nam wskaźnik? Przykładowo, dzięki wskaźnikowi tablicy możemy za pomocą kopiowania pamięci (CopyMemory) stworzyć kopię tej tablicy w innej tablicy, i to lepiej i szybciej niż w przypadku przepisywania wartości. Jeśli chodzi o wskaźniki do procedur, to pozwalają one utworzyć wątek, czyli uruchomić daną procedurę.
Tak samo możemy przekazać funkcji zmienną poprzez wartość (ByVal) lub poprzez wskaźnik (ByRef). Na czym polega różnica? Na tym, że w pierwszym przypadku funkcja może operować na wartości, a w drugim na samej zmiennej (i dokonywać w niej zmian). Np.:
Call JakasFunkcja(ByRef Zmienna)
W powyższym przypadku jakaś funkcja może dokonywać zmian w Zmiennej, jeśli ByRef byłby zastąpiony przez ByVal to sytuacja taka nie mogłaby mieć miejsca

Aby pobrać wskaźnik funkcji należy użyć operatora AddressOf. Niedawno w artykule Karola Kuczmarskiego, pt. "Procedury i Funkcje" z VBM #12 wyczytałem, że nie można zapisać w VB wskaźnika do zmiennej. Do niedawna i ja tak sądziłem, ale wymyśliłem, że zrobię to tak:
Otóż wskaźniki wywołane (pobrane) za pomocą operatora AddressOf nie mogą być zapisane do zmiennej, a jedynie przekazane w postaci argumentu. Argumentu i nie tylko funkcjom API, bo funkcje API są dla kodu programu takie same jak funkcje wew. programu, ale o nieznanym adresie pamięci (tylko do momentu wywołania; technika późnego wiązania). Co z tego wynika? Możemy zrobić funkcję, której argument będzie wskaźnikiem (przekazanym przez operator AddressOf), a potem funkcja ta zwróci nam ten wskaźnik! Funkcja taka, może mieć postać:

Function GetAddress(AddressStatement As Long) As Long
GetAddress = AddressStatement
End Function

Później wywołanie takiej funkcji nie stanowi żadnego problemu, np:

MsgBox GetAddress(AddressOf GetAddress)

Czyż to nie piękne? Cóż ja mówię (a raczej piszę), to wspaniałe. Nie tylko to mam do przedstawienia w tym artykule. Jeszcze jedna rzecz: wywoływanie funkcji API bez deklaracji!!!  Lecz znów pojawia się problem, bo jak na razie nie potrafię przekazywać wywoływanym za pomocą adresu funkcją (lub Sub'om) parametrów. Poniżej przedstawiłem, jak możemy użyć Sub'a FatalExit. To nieprzyjemna operacja (chodzi o FatalExit), bo powoduje ona pojawienie się okienka, że program wykonał niedozwoloną operację i musi być zamknięty (lub bez pojawienia się tego okienka, następuje zamknięcie). Deklaracje (moduł):

Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Declare Function
GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Declare Function
CreateThread Lib "kernel32" (lpThreadAttributes As Any, ByVal dwStackSize As Long, ByVal lpStartAddress As Long, lpParameter As Any, ByVal dwCreationFlags As Long, lpThreadID As Long) As Long
Declare Function
TerminateThread Lib "kernel32" (ByVal hThread As Long, ByVal dwExitCode As Long) As Long
Declare Function
CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Global
hThread As Long, hThreadID As Long

LoadLibary służy do załadowania biblioteki DLL, GetProcAddress pobiera z tej biblioteki wskaźnik danej procedury. Następnie CreateThread tworzy nowy wątek (czyli wywołuje daną procedurę) za pomocą adresu procedury, CloseHandle zamyka uchwyt wątku, a TerminateThread zakańcza wątek (nawet jeśli ten się nie zakończył).
Dalej piszemy w kodzie (np. Form_Load):

Dim hCommand, hKernel32 As Long
hKernel32 = LoadLibrary("kernel32.dll")
hCommand = GetProcAddress(hKernel32, "FatalExit")
hThread = CreateThread(ByVal 0&, ByVal 0&, hCommand, 0, 0, hThreadID)
hThread = 0
CloseHandle hThread


Dalej piszemy w kodzie (Form_Unload):

If hThread <> 0 Then TerminateThread hThread, 0

A teraz uwaga!!! Lepiej skompilować plik i dopiero go uruchomić, bo VB zostanie zamknięty!
Innego przykładu dać nie mogłem, nie przekazałbym mu parametru. FatalExit może zmylić, bo wydaje nam się, że wystąpił błąd, a w rzeczywistości to wywołana funkcja odczytuje z jakiego modułu było ostatnie wywołanie i przedstawia go jako źródło błędu. Jaki jest dowód tego, że to nie błąd a wywołana funkcja? Pomimo ukazania się okienka, aplikacja może kontynuować działanie (można zamknąć, przesuwać oknem).

Jeśli ktoś z was ma jakiekolwiek pytania/uwagi lub wskazówki dot. przekazywania parametrów to niech pisze.

Marcin Porębski ( Doogie )

marcin.porebski@interia.pl